#pragma once
#include "Runtime/Base/ThreadSafeCounter.h"
#include "ThreadNames.h"
#include "WorkerThread.h"
#include "RunnableTaskEvent.h"
#include "Runtime/Debug/Logger.h"

namespace BattleFire
{
    class RunnableTask
    {
    protected:
        RunnableTask(BattleFireSInt32 inNumberOfPrerequistitesOutstanding)
            : ThreadToExecuteOn(ENamedThreads::AnyThread)
            , NumberOfPrerequistitesOutstanding(inNumberOfPrerequistitesOutstanding + 1)
        {
        }
        void SetThreadToExecuteOn(ENamedThreads inThreadToExecuteOn)
        {
            ThreadToExecuteOn = inThreadToExecuteOn;
        }
        void PrerequisitesComplete(ENamedThreads inCurrentThread, BattleFireSInt32 inNumAlreadyFinishedPrequistes, bool bUnlock = true)
        {
            BattleFireSInt32 NumToSub = inNumAlreadyFinishedPrequistes + (bUnlock ? 1 : 0);
            if (NumberOfPrerequistitesOutstanding.Subtract(NumToSub) == NumToSub) 
            {
                QueueTask(inCurrentThread);
            }
        }
        virtual ~RunnableTask()
        {
        }
    public:
    	void ConditionalQueueTask(ENamedThreads CurrentThread)
    	{
    		if (NumberOfPrerequistitesOutstanding.Decrement()==0)
    		{
    			QueueTask(CurrentThread);
    		}
    	}
        virtual void ExecuteTask(std::vector<RunnableTask*>& NewTasks, ENamedThreads CurrentThread)=0;
        FORCEINLINE void Execute(std::vector<RunnableTask*>& NewTasks, ENamedThreads CurrentThread)
        {
            ExecuteTask(NewTasks, CurrentThread);
        }
        void QueueTask(ENamedThreads CurrentThreadIfKnown);
        ENamedThreads				ThreadToExecuteOn;
        FThreadSafeCounter			NumberOfPrerequistitesOutstanding;
    };
	class XRunnableTask:public RunnableTask
	{
	public:
		XRunnableTask(BattleFireSInt32 InNumberOfPrerequistitesOutstanding)
			: RunnableTask(InNumberOfPrerequistitesOutstanding){
		}
		virtual ~XRunnableTask()
		{
		}
	public:
		virtual void ExecuteTask(std::vector<RunnableTask*>& NewTasks, ENamedThreads CurrentThread)
		{
			Debug("ExecuteTask XRunnableTask");
		}
	};
	enum class ESubsequentsMode
	{
		/** Necessary when another task will depend on this task. */
		TrackSubsequents,
		/** Can be used to save task graph overhead when firing off a task that will not be a dependency of other tasks. */
		FireAndForget
	};
	template<typename SomeRunnableTaskType>
	class TRunnableTask final : public RunnableTask
	{
	public:
		/** 
		 *	This is a helper class returned from the factory. It constructs the embeded task with a set of arguments and sets the task up and makes it ready to execute.
		 *	The task may complete before these routines even return.
		 **/
		class FConstructor
		{
		public:
			/** Passthrough internal task constructor and dispatch. Note! Generally speaking references will not pass through; use pointers */
			template<typename...T>
			RunnableTaskEventRef ConstructAndDispatchWhenReady(T&&... Args)
			{
				new ((void *)&Owner->TaskStorage) SomeRunnableTaskType(std::forward<T>(Args)...);
				return Owner->Setup(Prerequisites, FireThread);
			}

			/** Passthrough internal task constructor and hold. */
			template<typename...T>
			TRunnableTask* ConstructAndHold(T&&... Args)
			{
				new ((void *)&Owner->TaskStorage) SomeRunnableTaskType(std::forward<T>(Args)...);
				return Owner->Hold(Prerequisites, FireThread);
			}

		private:
			friend class TRunnableTask;

			/** The task that created me to assist with embeded task construction and preparation. **/
			TRunnableTask*								Owner;
			/** The list of prerequisites. **/
			const RunnableTaskEventArray*			Prerequisites;
			/** If known, the current thread.  ENamedThreads::AnyThread is also fine, and if that is the value, we will determine the current thread, as needed, via TLS. **/
			ENamedThreads								FireThread;

			/** Constructor, simply saves off the arguments for later use after we actually construct the embeded task. **/
			FConstructor(TRunnableTask* InOwner, const RunnableTaskEventArray* InPrerequisites, ENamedThreads InFireThread)
				: Owner(InOwner)
				, Prerequisites(InPrerequisites)
				, FireThread(InFireThread)
			{
			}
			/** Prohibited copy construction **/
			FConstructor(const FConstructor& Other){}
			/** Prohibited copy **/
			void operator=(const FConstructor& Other){}
		};

		/** 
		 *	Factory to create a task and return the helper object to construct the embedded task and set it up for execution.
		 *	@param Prerequisites; the list of FGraphEvents that must be completed prior to this task executing.
		 *	@param CurrentThreadIfKnown; provides the index of the thread we are running on. Can be ENamedThreads::AnyThread if the current thread is unknown.
		 *	@return a temporary helper class which can be used to complete the process.
		**/
		static FConstructor CreateTask(const RunnableTaskEventArray* Prerequisites = NULL, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			BattleFireSInt32 NumPrereq = (BattleFireSInt32)(Prerequisites ? Prerequisites->size() : 0);
			return FConstructor(new TRunnableTask(SomeRunnableTaskType::GetSubsequentsMode() == ESubsequentsMode::FireAndForget ? NULL : RunnableTaskEvent::CreateGraphEvent(), NumPrereq), Prerequisites, CurrentThreadIfKnown);
		}

		void Unlock(ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			ConditionalQueueTask(CurrentThreadIfKnown);
		}

		RunnableTaskEventRef GetCompletionEvent()
		{
			return Subsequents;
		}

	private:
		friend class FConstructor;
		friend class AliceThreadPoolEvent;
		void ExecuteTask(std::vector<RunnableTask*>& NewTasks, ENamedThreads CurrentThread)
		{
			if (SomeRunnableTaskType::GetSubsequentsMode() == ESubsequentsMode::TrackSubsequents)
			{
				Subsequents->CheckDontCompleteUntilIsEmpty(); // we can only add wait for tasks while executing the task
			}
			
			SomeRunnableTaskType& Task = *(SomeRunnableTaskType*)&TaskStorage;
			{
				Task.DoTask(CurrentThread, Subsequents);
				Task.~SomeRunnableTaskType();
			}
			
			TaskConstructed = false;

			if (SomeRunnableTaskType::GetSubsequentsMode() == ESubsequentsMode::TrackSubsequents)
			{
				PlatformMisc::MemoryBarrier();
				Subsequents->DispatchSubsequents(NewTasks, CurrentThread);
			}
			delete this;
		}
		TRunnableTask(RunnableTaskEventRef InSubsequents, BattleFireSInt32 NumberOfPrerequistitesOutstanding)
			: RunnableTask(NumberOfPrerequistitesOutstanding)
			, TaskConstructed(false)
		{
			std::swap(Subsequents,InSubsequents);
		}
		~TRunnableTask() override{}
		void SetupPrereqs(const RunnableTaskEventArray* Prerequisites, ENamedThreads CurrentThreadIfKnown, bool bUnlock)
		{
			TaskConstructed = true;
			SomeRunnableTaskType& Task = *(SomeRunnableTaskType*)&TaskStorage;
			SetThreadToExecuteOn(Task.GetDesiredThread());
			BattleFireUInt32 AlreadyCompletedPrerequisites = 0;
			if (Prerequisites)
			{
				for (BattleFireUInt32 Index = 0; Index < Prerequisites->size(); Index++)
				{
					const RunnableTaskEventRef & Prerequisite = (*Prerequisites)[Index];
					if (Prerequisite == nullptr || !Prerequisite->AddSubsequent(this))
					{
						AlreadyCompletedPrerequisites++;
					}
				}
			}
			PrerequisitesComplete(CurrentThreadIfKnown, AlreadyCompletedPrerequisites, bUnlock);
		}
		RunnableTaskEventRef Setup(const RunnableTaskEventArray* Prerequisites = NULL, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			RunnableTaskEventRef ReturnedDependencyPtr = Subsequents; // very important so that this doesn't get destroyed before we return
			SetupPrereqs(Prerequisites, CurrentThreadIfKnown, true);
			return ReturnedDependencyPtr;
		}
		TRunnableTask* Hold(const RunnableTaskEventArray* Prerequisites = NULL, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			SetupPrereqs(Prerequisites, CurrentThreadIfKnown, false);
			return this;
		}
	public:
		static FConstructor CreateTask(RunnableTaskEventRef SubsequentsToAssume, const RunnableTaskEventArray* Prerequisites = NULL, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			return FConstructor(new TRunnableTask(SubsequentsToAssume, Prerequisites ? Prerequisites->size() : 0), Prerequisites, CurrentThreadIfKnown);
		}
		TAlignedBytes<sizeof(SomeRunnableTaskType),alignof(SomeRunnableTaskType)>	TaskStorage;
		bool																		TaskConstructed;
		RunnableTaskEventRef													Subsequents;
	};
	class FReturnGraphTask
	{
	public:
		FReturnGraphTask(ENamedThreads InThreadToReturnFrom)
			: ThreadToReturnFrom(InThreadToReturnFrom)
		{
		}
		ENamedThreads GetDesiredThread()
		{
			return ThreadToReturnFrom;
		}
		static ESubsequentsMode GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
		void DoTask(ENamedThreads CurrentThread, const RunnableTaskEventRef& MyCompletionGraphEvent);

	private:
		/** Named thread that we want to cause to return to the caller of ProcessThreadUntilRequestReturn. **/
		ENamedThreads ThreadToReturnFrom;
	};
	class FTriggerEventGraphTask
	{
	public:
		FTriggerEventGraphTask(BattleFireEvent* InEvent, ENamedThreads InDesiredThread = ENamedThreads::AnyHiPriThreadHiPriTask)
			: Event(InEvent) 
			, DesiredThread(InDesiredThread)
		{
		}
		ENamedThreads GetDesiredThread()
		{
			return DesiredThread;
		}

		static ESubsequentsMode GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }

		void DoTask(ENamedThreads CurrentThread, const RunnableTaskEventRef& MyCompletionGraphEvent)
		{
			Event->TriggerAll();
		}
	private:
		BattleFireEvent* Event;
		/** Thread to run on, can be ENamedThreads::AnyThread **/
		ENamedThreads DesiredThread;
	};
	class FNullGraphTask
	{
	public:
		/** 
		*	Constructor
		*	@param StatId The stat id for this task.
		*	@param InDesiredThread; Thread to run on, can be ENamedThreads::AnyThread
		**/
		FNullGraphTask(ENamedThreads InDesiredThread)
			: DesiredThread(InDesiredThread)
		{
		}

		/** 
		*	Retrieve the thread that this task wants to run on.
		*	@return the thread that this task should run on.
		**/
		ENamedThreads GetDesiredThread()
		{
			return DesiredThread;
		}

		static ESubsequentsMode GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }

		/** 
		*	Actually execute the task.
		*	@param	CurrentThread; the thread we are running on
		*	@param	MyCompletionGraphEvent; my completion event. Not always useful since at the end of DoWork, you can assume you are done and hence further tasks do not need you as a prerequisite. 
		*	However, MyCompletionGraphEvent can be useful for passing to other routines or when it is handy to set up subsequents before you actually do work.
		**/
		void DoTask(ENamedThreads CurrentThread, const RunnableTaskEventRef& MyCompletionGraphEvent)
		{
		}
	private:
		/** Thread to run on, can be ENamedThreads::AnyThread **/
		ENamedThreads DesiredThread;
	};
	class FinishThreadWorkTask
	{
	public:
		FinishThreadWorkTask(BattleFireEvent* InEvent, ENamedThreads InDesiredThread = ENamedThreads::AnyHiPriThreadHiPriTask)
			: Event(InEvent)
			, DesiredThread(InDesiredThread)
		{
		}
		ENamedThreads GetDesiredThread()
		{
			return DesiredThread;
		}

		static ESubsequentsMode GetSubsequentsMode() { return ESubsequentsMode::FireAndForget; }

		void DoTask(ENamedThreads CurrentThread, const RunnableTaskEventRef& MyCompletionGraphEvent)
		{
			Event->TriggerAll();
		}
	private:
		BattleFireEvent* Event;
		ENamedThreads DesiredThread;
	};
}
